// -*- mode:objc -*-
// $Id: PTYTextView.m,v 1.81 2003/08/13 05:03:54 sgehrman Exp $
/*
 **  PTYTextView.m
 **
 **  Copyright (c) 2002, 2003
 **
 **  Author: Fabian, Ujwal S. Sathyam
 **	     Initial code by Kiichi Kusama
 **
 **  Project: iTerm
 **
 **  Description: NSTextView subclass. The view object for the VT100 screen.
 **
 **  This program is free software; you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation; either version 2 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program; if not, write to the Free Software
 **  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define GREED_KEYDOWN         1

#import "iTerm.h"
#import "PTYTextView.h"
#import "PTYSession.h"
#import "VT100Screen.h"
#import "ITPreferences.h"
#import "ITContextualMenuMgr.h"

@implementation PTYTextView

- (void)commonInit;
{
    deadkey = NO;
    printingSelection = NO;
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(frameChanged:)
                                                 name:NSViewFrameDidChangeNotification
                                               object:self];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(myWindowDidBecomeOrResignKey:)
                                                 name:NSWindowDidBecomeKeyNotification
                                               object:nil];  // we check the window in the notification
}

- (id)initWithFrame: (NSRect) aRect
{
    self = [super initWithFrame: aRect];
    
    [self commonInit];
    
    return self;
}

- (id)initWithFrame:(NSRect)aRect textContainer:(NSTextContainer *)textContainer
{
    self = [super initWithFrame: aRect textContainer: textContainer];
    
    [self commonInit];
    
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [dataSource release];
    dataSource = nil;
    
    [font release];
    font = nil;
    
    [super dealloc];
}

- (BOOL)shouldDrawInsertionPoint
{
    return NO;
}

- (BOOL)antiAlias
{
    return antiAlias;
}

- (void)setAntiAlias: (BOOL) antiAliasFlag
{
    antiAlias = antiAliasFlag;
}

- (NSColor *) selectionColor
{
    NSDictionary *dic;
    
    dic = [self selectedTextAttributes];
    
    return ([dic objectForKey: NSBackgroundColorAttributeName]);
}

- (void) setSelectionColor: (NSColor *) aColor
{
    NSDictionary *dic;
    
    if(aColor != nil)
    {
        dic = [NSDictionary dictionaryWithObjectsAndKeys: aColor, NSBackgroundColorAttributeName, nil];
        [self setSelectedTextAttributes: dic];
    }
}

- (id) dataSource
{
    return (dataSource);
}

- (void)setDataSource: (id) aDataSource
{
    [dataSource autorelease];
    dataSource = [aDataSource retain];
}

- (float) lineHeight
{
    return lineHeight;
}

- (void) setLineHeight: (float) aLineHeight
{
    lineHeight = aLineHeight;
}

- (float) lineWidth
{
    return lineWidth;
}

- (void) setLineWidth: (float) aLineWidth
{
    lineWidth = aLineWidth;
}

- (NSFont *) font
{
    if(font == nil)
        return ([super font]);
    
    return (font);
}

- (void) setFont: (NSFont *)aFont
{
    [font autorelease];
    font = [aFont retain];
}

- (void) refresh
{
    NSSize aSize;
    
    if([self dataSource] != nil)
    {
        numberOfLines = [dataSource numberOfLines];
        aSize = [self frame].size;
        aSize.height = numberOfLines * lineHeight;
        
        if(aSize.height > [[self enclosingScrollView] documentVisibleRect].size.height)
        {
            NSRect aFrame;
            
            aFrame = [self frame];
            aFrame.size.height = aSize.height;
            [self setFrame: aFrame];
        }
        
        [self setNeedsDisplay: YES];
    }
}

-(void) scrollHome
{
    NSRect scrollRect;
    
    scrollRect= [self visibleRect];
    scrollRect.origin.y = 0;
    [self scrollRectToVisible: scrollRect];
}

- (void)scrollEnd
{
    if (numberOfLines > 0)
    {
        NSRect aFrame;
        
        aFrame.origin.x = 0;
        aFrame.origin.y = (numberOfLines - 1) * lineHeight;
        aFrame.size.width = [self frame].size.width;
        aFrame.size.height = lineHeight;
        
        [self scrollRectToVisible: aFrame];
    }
    else
        [self scrollRangeToVisible:NSMakeRange([[self textStorage] length],0)];
}

- (void)drawRect:(NSRect)rect
{
    // set the antialias flag
    [[NSGraphicsContext currentContext] setShouldAntialias: antiAlias];
    
    // Check if we are printing a selection
    if([NSGraphicsContext currentContextDrawingToScreen] == NO && printingSelection == YES)
    {
        NSRange selectedRange = [self selectedRange];
        NSRectArray rectArray;
        unsigned int rectCount, i;
        
        // get the array of rects that define the selected region
        rectArray = [[self layoutManager] rectArrayForCharacterRange: selectedRange withinSelectedCharacterRange: selectedRange inTextContainer: [self textContainer] rectCount: &rectCount];
        
        // draw all the rects
        for (i = 0; i < rectCount; i++)
        {
            NSRect theRect = *(rectArray + i);
            [super drawRect: theRect];
        }
    }
    else
        [super drawRect: rect];
    
#if 0  // to debug custom typesetting
    static int i;
    NSString *myString = [[self textStorage] string];
    //if(i <= 0)
    i = 0;
    
    while (i < [myString length])
    {
        unichar myChar = [myString characterAtIndex: i];
        NSLog(@"\nmyChar %d = '%c'", i, myChar);
        NSLog(@"container width = %f", [[self textContainer] containerSize].width);
        NSLog(@"textContainer inset = %f,%f", [self textContainerInset].width, [self textContainerInset].height);
        //if(myChar == '\n')
        {
            NSRange myRange = [[self layoutManager] glyphRangeForCharacterRange: NSMakeRange(i, 1) actualCharacterRange: nil];
            NSRange effectiveRange;
            NSLog(@"glyph range for char index %d = %d,%d", i, myRange.location, myRange.length);
            NSLogRect([[self layoutManager] lineFragmentRectForGlyphAtIndex: myRange.location effectiveRange: &effectiveRange]);
            NSLog(@"effective range = %d, %d", effectiveRange.location, effectiveRange.length);
        }
        i++;
    }
#endif 
}

- (void)keyDown:(NSEvent *)event
{
    NSString* characters = [event characters];
    unichar firstCharacter = [characters characterAtIndex:0];
    
    if (firstCharacter == NSPageUpFunctionKey || 
        firstCharacter == NSPageDownFunctionKey || 
        firstCharacter == NSHomeFunctionKey || 
        firstCharacter == NSEndFunctionKey)
        [super keyDown:event];
    else
    {
        NSInputManager *imana = [NSInputManager currentInputManager];
        BOOL IMEnable = [imana wantsToInterpretAllKeystrokes];
        id delegate = [self delegate];
        BOOL put;
        
        // Hide the cursor
        [NSCursor setHiddenUntilMouseMoves: YES];    
        
        // Check for dead keys if OPTION key is used as normal
        if ([[ITPreferences sharedInstance] optionKeyMode] == OPT_NORMAL)
        {
            if (deadkey) 
            {
                [self interpretKeyEvents:[NSArray arrayWithObject:event]];
                deadkey=[self hasMarkedText];
                return;
            }
            else if ([[event characters] length] < 1)
            {
                deadkey=YES;
                [self interpretKeyEvents:[NSArray arrayWithObject:event]];
                return;
            }
        }
        else
            deadkey=NO;
        
        if (IMEnable) 
        {
            BOOL prev = [self hasMarkedText];
            IM_INPUT_INSERT = NO;
            [self interpretKeyEvents:[NSArray arrayWithObject:event]];
            
#if GREED_KEYDOWN
            if (prev == NO && 
                IM_INPUT_INSERT == NO && 
                [self hasMarkedText] == NO) 
            {
                put = YES;
            }
            else
                put = NO;
#else
            put = NO;
#endif
        }
        else
            put = YES;
        
        if (put == YES)
        {
            if ([delegate respondsToSelector:@selector(keyDown:)])
                [delegate keyDown:event];
            else
                [super keyDown:event];
        }
    }
}

- (void)doCommandBySelector:(SEL)aSelector
{    
#if !GREED_KEYDOWN
    id delegate = [self delegate];
    
    if ([delegate respondsToSelector:aSelector]) {
        [delegate performSelector:aSelector withObject:nil];
    }
#endif
}

- (void)insertText:(id)aString
{
    id delegate = [self delegate];
    
    NSTextStorage *storage = [self textStorage];
    
    IM_INPUT_INSERT = YES;
    
    [storage beginEditing];
    [storage deleteCharactersInRange:[self markedRange]];
    [storage endEditing];
    IM_INPUT_MARKEDRANGE = NSMakeRange(0, 0);
    
    if ([delegate respondsToSelector:@selector(insertText:)])
        [delegate insertText:aString];
    else
        [super insertText:aString];
}

- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange
{
    NSRange repRange;
    NSTextStorage *storage = [self textStorage];
        
    if ([self hasMarkedText])
        repRange = [self markedRange];
    else
        repRange = NSMakeRange(cursorIndex, 0);

    [storage beginEditing];
    if ([aString isKindOfClass:[NSAttributedString class]])
    {
        [storage replaceCharactersInRange:repRange 
                     withAttributedString:aString];
        IM_INPUT_MARKEDRANGE = NSMakeRange(0, [(NSAttributedString *)aString length]);
    }
    else 
    {
        [storage replaceCharactersInRange:repRange
                               withString:aString];
        IM_INPUT_MARKEDRANGE = NSMakeRange(0,
                                           [(NSString *)aString length]);
    }
    IM_INPUT_SELRANGE = selRange;
    [storage endEditing];
    
    [self setNeedsDisplay:YES];
}

- (void)unmarkText
{
    IM_INPUT_MARKEDRANGE = NSMakeRange(0, 0);
}

- (BOOL)hasMarkedText
{
    BOOL result;
    
    if (IM_INPUT_MARKEDRANGE.length > 0)
        result = YES;
    else
        result = NO;
    
    return result;
}

- (NSRange)markedRange 
{
    //return IM_INPUT_MARKEDRANGE;
    if (IM_INPUT_MARKEDRANGE.length > 0) 
    {
        NSTextStorage *storage = [self textStorage];
        int len = [storage length];
        int toploc;
        
        toploc = len - IM_INPUT_MARKEDRANGE.length;
        return NSMakeRange(cursorIndex, IM_INPUT_MARKEDRANGE.length);
    }
    else
        return NSMakeRange(cursorIndex, 0);
}

- (void)clearScrollbackBuffer:(id)sender
{
    id delegate = [self delegate];
	
    if ([delegate respondsToSelector:@selector(clearScrollbackBuffer)])
        [delegate clearScrollbackBuffer];
}

- (void)clear:(id)sender
{
    id delegate = [self delegate];
	
    if ([delegate respondsToSelector:@selector(clearBuffer)])
        [delegate clearBuffer];
}

- (NSString *)copyAsString;
{
    NSString *aString;
    NSMutableAttributedString *aMutableAttributedString;
    int i = 0;
        
    aMutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString: [[self textStorage] attributedSubstringFromRange: [self selectedRange]]];
    [aMutableAttributedString autorelease];
    
    if((aMutableAttributedString == nil) || ([aMutableAttributedString length] == 0))
        return nil;
    
    // remove linewraps
    while (i < [aMutableAttributedString length])
    {
        if([aMutableAttributedString attribute: @"VT100LineWrap" atIndex: i effectiveRange: nil])
            [aMutableAttributedString deleteCharactersInRange: NSMakeRange(i, 1)];
        i++;
    }
    
    // Further process the string
    aString = [aMutableAttributedString string];
    if((aString == nil) || ([aString length] == 0))
        return nil;
    if([aString length] > 1) // Cocoa bug?
        aString = [aString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
    
    return aString;
}

// Override copy and paste to do our stuff
- (void) copy: (id) sender
{
    NSPasteboard *pboard = [NSPasteboard generalPasteboard];
    NSString *aString = [self copyAsString];
        
    if((aString == nil) || ([aString length] == 0))
        return;
    
    // Put the trimmed string on the pasteboard
    [pboard declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: self];
    [pboard setString: aString forType: NSStringPboardType];
}

- (void)paste:(id)sender
{
    id delegate = [self delegate];
        
    if ([delegate respondsToSelector:@selector(paste:)])
        [delegate paste:sender];
    else
        [super paste:sender];
}

- (void)pasteSelection:(id)sender;
{
    id delegate = [self delegate];
    NSString *aString = [self copyAsString];
    
    if((aString == nil) || ([aString length] == 0))
        return;
    
    if ([delegate respondsToSelector:@selector(pasteString:)])
        [delegate pasteString:aString];
}

- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    if ([item action] == @selector(paste:))
    {
        NSPasteboard *pboard = [NSPasteboard generalPasteboard];
        
        // Check if there is a string type on the pasteboard
        return ([pboard stringForType:NSStringPboardType] != nil);
    }
    else if ([item action ] == @selector(cut:))
        return NO;
    else if ([item action]==@selector(saveDocumentAs:))
    {
        // We always validate the "Save" command
        return (YES);
    }
    else if ([item action]==@selector(mail:) || 
             [item action]==@selector(browse:) ||
             [item action] == @selector(printSelection:) ||
             [item action] == @selector(pasteSelection:)) 
    {
        return ([self selectedRange].length>0);
    }
    else
        return [super validateMenuItem:item];
}

- (void)changeFont:(id)sender
{
    id delegate = [self delegate];
        
    if ([delegate respondsToSelector:@selector(changeFont:)])
        [delegate changeFont:sender];
    else
        [super changeFont:sender];
}

- (void) mail:(id)sender
{
    NSString *s = [[self string] substringWithRange:[self selectedRange]];
    NSURL *url;
    
    if (![s hasPrefix:@"mailto:"])
        url = [NSURL URLWithString:[@"mailto:" stringByAppendingString:s]];
    else
        url = [NSURL URLWithString:s];
    
    [[NSWorkspace sharedWorkspace] openURL:url];
}

- (void) browse:(id)sender
{
    NSString *s = [[self string] substringWithRange:[self selectedRange]];
    NSURL *url;
    
    // Check for common types of URLs
    if ([s hasPrefix:@"file://"])
        url = [NSURL URLWithString:s];
    else if ([s hasPrefix:@"ftp"])
    {
        if (![s hasPrefix:@"ftp://"])
            url = [NSURL URLWithString:[@"ftp://" stringByAppendingString:s]];
        else
            url = [NSURL URLWithString:s];
    }
    else if (![s hasPrefix:@"http"])
        url = [NSURL URLWithString:[@"http://" stringByAppendingString:s]];
    else
        url = [NSURL URLWithString:s];
    
    [[NSWorkspace sharedWorkspace] openURL:url];
}

- (void)frameChanged:(NSNotification*)notification
{
	// SNG - hack to prevent this getting caught in a loop
    /*if (!_inFrameChanged)
    {
        _inFrameChanged = YES;
        [[self delegate] textViewResized: self];
        _inFrameChanged = NO;
    }*/
}

//
// Drag and Drop methods for our text view
//

//
// Called when our drop area is entered
//
- (unsigned int) draggingEntered:(id <NSDraggingInfo>)sender
{
    // Always say YES; handle failure later.
    bExtendedDragNDrop = YES;
    
    return bExtendedDragNDrop;
}

//
// Called when the dragged object is moved within our drop area
//
- (unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender
{
    unsigned int iResult;
    
    // Let's see if our parent NSTextView knows what to do
    iResult = [super draggingUpdated: sender];
    
    // If parent class does not know how to deal with this drag type, check if we do.
    if (iResult == NSDragOperationNone) // Parent NSTextView does not support this drag type.
        return [self _checkForSupportedDragTypes: sender];
    
    return iResult;
}

//
// Called when the dragged object leaves our drop area
//
- (void) draggingExited:(id <NSDraggingInfo>)sender
{
    // We don't do anything special, so let the parent NSTextView handle this.
    [super draggingExited: sender];
    
    // Reset our handler flag
    bExtendedDragNDrop = NO;
}

//
// Called when the dragged item is about to be released in our drop area.
//
- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender
{
    BOOL bResult;

    // Check if parent NSTextView knows how to handle this.
    bResult = [super prepareForDragOperation: sender];
    
    // If parent class does not know how to deal with this drag type, check if we do.
    if ( bResult != YES && [self _checkForSupportedDragTypes: sender] != NSDragOperationNone )
        bResult = YES;
    
    return bResult;
}

//
// Called when the dragged item is released in our drop area.
//
- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
    unsigned int dragOperation;
    BOOL bResult = NO;
    PTYSession *delegate = [self delegate];
        
    // If parent class does not know how to deal with this drag type, check if we do.
    if (bExtendedDragNDrop)
    {
        NSPasteboard *pb = [sender draggingPasteboard];
        NSArray *propertyList;
        NSString *aString;
        int i;
        
        dragOperation = [self _checkForSupportedDragTypes: sender];
        
        switch (dragOperation)
        {
            case NSDragOperationCopy:
                // Check for simple strings first
                aString = [pb stringForType:NSStringPboardType];
                if (aString != nil)
                {
                    if ([delegate respondsToSelector:@selector(pasteString:)])
                        [delegate pasteString: aString];
                    else
                        [super paste:sender];
                }
                    // Check for file names
                    propertyList = [pb propertyListForType: NSFilenamesPboardType];
                for(i = 0; i < [propertyList count]; i++)
                {
                    // Ignore text clippings
                    NSString *filename = (NSString*)[propertyList objectAtIndex: i]; // this contains the POSIX path to a file
                    NSDictionary *filenamesAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:filename traverseLink:YES];
                    if (([filenamesAttributes fileHFSTypeCode] == 'clpt' &&
                         [filenamesAttributes fileHFSCreatorCode] == 'MACS') ||
                        [[filename pathExtension] isEqualToString:@"textClipping"] == YES)
                    {
                        continue;
                    }
                    
                    // Just paste the file names into the shell after escaping special characters.
                    if ([delegate respondsToSelector:@selector(pasteString:)])
                    {
                        NSMutableString *aMutableString;
                        
                        aMutableString = [[NSMutableString alloc] initWithString: (NSString*)[propertyList objectAtIndex: i]];
                        // get rid of special characters
                        [aMutableString replaceOccurrencesOfString: @"\\" withString: @"\\\\" options: 0 range: NSMakeRange(0, [aMutableString length])];
                        [aMutableString replaceOccurrencesOfString: @" " withString: @"\\ " options: 0 range: NSMakeRange(0, [aMutableString length])];
                        [aMutableString replaceOccurrencesOfString: @"(" withString: @"\\(" options: 0 range: NSMakeRange(0, [aMutableString length])];
                        [aMutableString replaceOccurrencesOfString: @")" withString: @"\\)" options: 0 range: NSMakeRange(0, [aMutableString length])];
                        [aMutableString replaceOccurrencesOfString: @"\"" withString: @"\\\"" options: 0 range: NSMakeRange(0, [aMutableString length])];
    [aMutableString replaceOccurrencesOfString: @"&" withString: @"\\&" options: 0 range: NSMakeRange(0, [aMutableString length])];
    [aMutableString replaceOccurrencesOfString: @"'" withString: @"\\'" options: 0 range: NSMakeRange(0, [aMutableString length])];

    [delegate pasteString: aMutableString];
    [delegate pasteString: @" "];
    [aMutableString release];
                    }
                }
    bResult = YES;
    break;				
        }
    }

    return bResult;
}

//
//
//
- (void) concludeDragOperation:(id <NSDraggingInfo>)sender
{    
    // If we did no handle the drag'n'drop, ask our parent to clean up
    // I really wish the concludeDragOperation would have a useful exit value.
    if (!bExtendedDragNDrop)
        [super concludeDragOperation: sender];
    
    bExtendedDragNDrop = NO;
}

// Print
- (void) print: (id) sender
{
    NSPrintInfo *aPrintInfo;
    
    aPrintInfo = [NSPrintInfo sharedPrintInfo];
    [aPrintInfo setHorizontalPagination: NSFitPagination];
    [aPrintInfo setVerticalPagination: NSAutoPagination];
    
    [[NSPrintOperation printOperationWithView: self  printInfo: aPrintInfo] runOperation];
}

// Print selection
- (void) printSelection: (id) sender
{
    if([self selectedRange].length <= 0)
    {
        NSBeep();
        return;
    }
    printingSelection = YES;
    
    [self print: self];
    
    printingSelection = NO;
}

// Save method
- (void) saveDocumentAs: (id) sender
{
    NSString *aString;
    NSData *aData;
    NSSavePanel *aSavePanel;
        
    // We get our content of the textview or selection, if any
    if([self selectedRange].length > 0)
        aString = [[self string] substringWithRange: [self selectedRange]];
    else
        aString = [self string];
    aData = [aString dataUsingEncoding: NSASCIIStringEncoding allowLossyConversion: YES];
    // retain here so that is does not go away...        
    [aData retain];
    
    // initialize a save panel
    aSavePanel = [NSSavePanel savePanel];
    [aSavePanel setAccessoryView: nil];
    [aSavePanel setRequiredFileType: @""];
    
    // Run the save panel as a sheet
    [aSavePanel beginSheetForDirectory: @"" 
                                  file: @"Unknown" 
                        modalForWindow: [self window]
                         modalDelegate: self 
                        didEndSelector: @selector(_savePanelDidEnd: returnCode: contextInfo:) 
                           contextInfo: aData];
}

- (void)mouseDown:(NSEvent *)theEvent
{
    // Check if the delegate will handle the event
    id delegate = [self delegate];
    if([delegate respondsToSelector: @selector(willHandleEvent:)] &&
       [delegate willHandleEvent: theEvent])
        [delegate handleEvent: theEvent];
    else
        [super mouseDown: theEvent];
}

- (void) setCursorIndex:(int) idx
{
    cursorIndex=idx;
}

- (BOOL)becomeFirstResponder;
{
    BOOL result = [super becomeFirstResponder];
    [dataSource updateCursor:YES];
    return result;
}

- (BOOL)resignFirstResponder;
{
    BOOL result = [super resignFirstResponder];
    [dataSource updateCursor:NO];
	return result;
}

- (void)myWindowDidBecomeOrResignKey:(NSNotification *)notification;
{
    if ([notification object] == [self window])
    {
        // update the cursor
        [dataSource showCursor];
    }
}

- (NSMenu*)menuForEvent:(NSEvent*)event;
{
    return [[ITContextualMenuMgr sharedInstance] terminalContextualMenu];
}

@end

//
// private methods
//
@implementation PTYTextView (Private)

- (unsigned int) _checkForSupportedDragTypes:(id <NSDraggingInfo>) sender
{
    NSString *sourceType;
    BOOL iResult;
    
    iResult = NSDragOperationNone;
    
    // We support the FileName drag type for attching files
    sourceType = [[sender draggingPasteboard] availableTypeFromArray: [NSArray arrayWithObjects: 
        NSFilenamesPboardType, 
        NSStringPboardType, 
        nil]];
    
    if (sourceType)
        iResult = NSDragOperationCopy;
    
    return iResult;
}

- (void) _savePanelDidEnd: (NSSavePanel *) theSavePanel
               returnCode: (int) theReturnCode
              contextInfo: (void *) theContextInfo
{
    // If successful, save file under designated name
    if (theReturnCode == NSOKButton)
    {
        if ( ![(NSData *)theContextInfo writeToFile: [theSavePanel filename] atomically: YES] )
            NSBeep();
    }
    // release our hold on the data
    [(NSData *)theContextInfo release];
}


@end
